package it.tukano.fun;

import java.util.Iterator;

/**
 * A value that can be null
 * @author pgi
 * @param <T>
 */
public class OptionalValue<T> implements Iterable<T> {

    /**
     * Instance creator
     * @param <T> the value type
     * @param arg the value
     * @return a new optionalvalue
     */
    public static <T> OptionalValue<T> newInstance(T arg) {
        return new OptionalValue<T>(arg);
    }
    
    private final T value;

    /**
     * Instance initializer
     */
    public OptionalValue(T value) {
        this.value = value;
    }

    /**
     * Check if this value is null
     * @return true if the value held by this object is null
     */
    public boolean isEmpty() {
        return value == null;
    }

    /**
     * Return the value of this optional value or the given value if this is
     * null
     * @param alt the alternative value
     * @return the value of this optional value or alt if isNull == true
     */
    public T getOrElse(T alt) {
        return isEmpty() ? alt : value;
    }

    /**
     * Returns the value of this optional value
     * @return the value of this optiona value. Maybe null
     */
    public T get() {
        return value;
    }

    /**
     * Checks if the value is not null
     * @return true if the value is not null
     */
    public boolean isDefined() {
        return value != null;
    }

    /**
     * Executes a function if the value of this optional value is not null
     * @param <RESULT> the type of the result
     * @param fun the function to execute
     * @return the result of fun applied to the value of this optionalvalue
     */
    public <RESULT> RESULT ifDefined(Function1<T, RESULT> fun) {
        return fun.apply(get());
    }

    /**
     * Checks if this optional value contains the given value
     * @param value the value to check
     * @return true if isDefined and get().equals(value)
     */
    public boolean hasValue(T value) {
        return isDefined() && get().equals(value);
    }

    /**
     * Checks if the value of this option intersects the given set of elements
     * @param values the set of values to check for intersection
     * @return true if defined and values.contains(value)
     */
    public boolean intersects(T... values) {
        if(isDefined()) {
            for (int i = 0; i < values.length; i++) {
                T t = values[i];
                if(value.equals(t)) return true;
            }
            return false;
        } else {
            return false;
        }
    }

    /**
     * Returns an iterator over the elements of this value
     * @return the iterator
     */
    public Iterator<T> iterator() {
        return new Iterator<T>() {
            boolean end = value == null;

            public boolean hasNext() {
                return !end;
            }

            public T next() {
                end = true;
                return value;
            }

            public void remove() {
                throw new UnsupportedOperationException("Not supported yet.");
            }
        };
    }

    @Override
    public String toString() {
        return String.format("Option[%s]", get());
    }

}
